package com.wuyan.web.auth.helper;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wuyan.helper.kit.RandomHelper;
import com.wuyan.web.auth.repo.*;
import com.wuyan.web.base.entity.*;
import com.wuyan.web.base.helper.SpringRedisHelper;
import com.wuyan.web.base.helper.auth.LoginInfo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toList;

/**
 * 授权信息相关工具
 *
 * @author wuyan
 */

@Component
@Slf4j
public class AuthHelper {
    @Autowired
    private ObjectMapper mapper;

    private static final String SESSION_AUTH_LOGIN_NAME = "auth-login";

    /**
     * 存放于响应头部的token名称
     */
    public static final String HEADER_AUTH_NAME = "authorization";

    @Autowired
    private SpringRedisHelper springRedisHelper;

    @Autowired
    private PubRoleApiRepoExtend pubRoleApiRepoExtend;

    @Autowired
    private PubRoleRepoExtend pubRoleRepoExtend;

    @Autowired
    private PubApiRepoExtend pubApiRepoExtend;

    @Autowired
    private PubFunctionRepoExtend pubFunctionRepoExtend;

    @Autowired
    private PubDeptRepo pubDeptRepo;

    @Autowired
    private PubPostRepo pubPostRepo;

    @Autowired
    private PubAccountRepo pubAccountRepo;

    @Autowired
    private PubUserRepo pubUserRepo;

    /**
     * 保存登录信息, 并生成token
     *
     * @param request        请求体
     * @param pubAccount     账户信息
     * @param pubUser        用户信息
     * @param tokenExpiresIn token有效时间
     * @return LoginInfo
     */
    public LoginInfo save(HttpServletRequest request,
                          HttpServletResponse response,
                          PubAccount pubAccount,
                          PubUser pubUser,
                          long tokenExpiresIn) throws JsonProcessingException {
        HttpSession session = request.getSession();
        String accessToken = SESSION_AUTH_LOGIN_NAME + "-" + RandomHelper.createAccessToken();

        List<PubRole> roles = getRole(pubAccount);
        LoginInfo loginInfo = LoginInfo.builder()
                .token(accessToken)
                .expiresIn(tokenExpiresIn)
                .account(pubAccount)
                .user(pubUser)
                .dept(null == pubAccount.getDept() ? null : getDept(pubAccount))
                .posts(StringUtils.isBlank(pubAccount.getPost()) ? null : getPosts(pubAccount))
                .roles(roles)
                .apis(getApi(roles))
                .menus(getMenus(roles))
                .loginTime(LocalDateTime.now())
                .refreshTime(LocalDateTime.now())
                .build();

        saveToSession(session, loginInfo);
        saveToCache(loginInfo);

        response.setHeader(HEADER_AUTH_NAME, accessToken);

        return loginInfo;
    }

    /**
     * 获取部门信息
     *
     * @param pubAccount 账户信息
     * @return PubDept
     */
    public PubDept getDept(PubAccount pubAccount) {
        com.wuyan.web.auth.entity.PubDept pubDept = pubDeptRepo.findById(pubAccount.getDept()).orElse(null);
        PubDept build = PubDept.builder().build();
        if (null == pubDept) {
            return build;
        }

        BeanUtils.copyProperties(pubDept, build);
        return build;
    }

    /**
     * 获取职位信息
     *
     * @param pubAccount 账户信息
     * @return List<PubPost>
     */
    public List<PubPost> getPosts(PubAccount pubAccount) {
        List<com.wuyan.web.auth.entity.PubPost> posts = pubPostRepo.findAllById(
                Arrays.stream(pubAccount.getPost().split(",")).map(Integer::parseInt).collect(Collectors.toSet())
        );

        List<PubPost> pubPosts = new ArrayList<>();

        posts.forEach(t -> {
            PubPost build = PubPost.builder().build();
            BeanUtils.copyProperties(t, build);
            pubPosts.add(build);
        });

        return pubPosts;
    }

    @SneakyThrows
    public boolean update(HttpSession session, LoginInfo loginInfo) {
        saveToSession(session, loginInfo);
        saveToCache(loginInfo);
        return true;
    }

    /**
     * 获取账户角色,已经启用的
     *
     * @param pubAccount 账户信息
     * @return List<PubAccountRole>
     */
    public List<PubRole> getRole(PubAccount pubAccount) {
        List<PubRole> data = new ArrayList<>();

        String roleIdStr = pubAccount.getRole();
        if (StringUtils.isBlank(roleIdStr)) {
            return data;
        }

        String[] roleIds = roleIdStr.split(",");

        List<com.wuyan.web.auth.entity.PubRole> roles =
                pubRoleRepoExtend.findAllByIdInAndStatus(Arrays.stream(roleIds)
                        .map(Integer::parseInt)
                        .collect(toList()), 1);

        roles.forEach(t -> {
            PubRole build = PubRole.builder().build();
            BeanUtils.copyProperties(t, build);
            data.add(build);
        });

        return data;
    }

    /**
     * 获取角色下的已授权接口信息
     *
     * @param roles 角色组
     * @return List<PubRoleApi>
     */
    public List<PubApi> getApi(List<PubRole> roles) {
        List<PubApi> data = new ArrayList<>();

        List<Integer> ids = roles.stream().map(PubRole::getId).collect(toList());
        List<com.wuyan.web.auth.entity.PubRoleApi> pubRoleApis =
                pubRoleApiRepoExtend.findAllByRole(ids);

        if (null == pubRoleApis || pubRoleApis.isEmpty()) {
            return data;
        }

        List<com.wuyan.web.auth.entity.PubApi> apis =
                pubApiRepoExtend.findAllByIdInAndStatus(
                        pubRoleApis.stream()
                                .map(com.wuyan.web.auth.entity.PubRoleApi::getRoleId)
                                .collect(toList()), 1);

        apis.forEach(t -> {
            PubApi build = PubApi.builder().build();
            BeanUtils.copyProperties(t, build);
            data.add(build);
        });

        return data;
    }

    /**
     * 获取角色下的已授权菜单
     *
     * @param roles 角色组
     * @return List<PubRoleFunction>
     */
    public List<PubFunction> getMenus(List<PubRole> roles) {
        List<PubFunction> data = new ArrayList<>();

        final String[] menuIdStrArray = {""};
        roles.forEach(t -> {
            if (StringUtils.isNotBlank(t.getMenuIds())) {
                menuIdStrArray[0] = menuIdStrArray[0] + "," + t.getMenuIds();
            }
        });
        if (StringUtils.isBlank(menuIdStrArray[0])) {
            return data;
        }

        String[] menuIdStr = menuIdStrArray[0].split(",");

        List<com.wuyan.web.auth.entity.PubFunction> menus =
                pubFunctionRepoExtend.findAllByIdInAndStatus(Arrays.stream(menuIdStr)
                        .filter(t -> StringUtils.isNotBlank(t) && StringUtils.isNumericSpace(t))
                        .map(Integer::parseInt)
                        .collect(Collectors.toSet()), 1);

        menus.forEach(t -> {
            PubFunction build = PubFunction.builder().build();
            BeanUtils.copyProperties(t, build);
            data.add(build);
        });

        return data;
    }

    /**
     * 获取登录信息
     *
     * @param session 会话
     * @param token   临时凭据
     * @return LoginInfo
     */
    public LoginInfo get(HttpSession session, String token) throws IOException {
        LoginInfo loginInfo = getBySession(session);
        if (null == loginInfo) {
            loginInfo = getByCache(token);

            if (null != loginInfo) {
                return expire(session, token, loginInfo);
            }
        } else {
            return expire(session, token, loginInfo);
        }

        return null;
    }

    /**
     * 保存登录信息到缓存器
     *
     * @param loginInfo 登录信息
     */
    private void saveToCache(LoginInfo loginInfo) throws JsonProcessingException {
        springRedisHelper.set(loginInfo.getToken(), mapper.writeValueAsString(loginInfo), loginInfo.getExpiresIn());
    }

    /**
     * 保存登录信息到session
     *
     * @param session   会话
     * @param loginInfo 登录信息
     */
    private void saveToSession(HttpSession session, LoginInfo loginInfo) throws JsonProcessingException {
        session.setAttribute(SESSION_AUTH_LOGIN_NAME, mapper.writeValueAsString(loginInfo));
    }

    /**
     * 从session中获取登录信息
     *
     * @param session 会话
     * @return LoginInfo
     */
    private LoginInfo getBySession(HttpSession session) throws IOException {
        String res = (String) session.getAttribute(SESSION_AUTH_LOGIN_NAME);

        if (null == res) {
            return null;
        }

        return mapper.readValue(res.getBytes(), LoginInfo.class);
    }

    /**
     * 从缓存中获取登录信息
     *
     * @param token 临时凭据
     * @return LoginInfo
     */
    private LoginInfo getByCache(String token) throws IOException {
        String res = springRedisHelper.get(token, String.class);

        if (StringUtils.isBlank(res)) {
            return null;
        }

        return mapper.readValue(res.getBytes(), LoginInfo.class);
    }

    /**
     * 清除登录信息
     *
     * @param session 会话
     * @param token   临时授权麻
     * @throws IOException e
     */
    private void remove(HttpSession session, String token) throws IOException {
        session.removeAttribute(SESSION_AUTH_LOGIN_NAME);
        springRedisHelper.remove(token);
    }

    /**
     * 判断登录是否过期，如果没有，并且即将到过期时间，则刷新过期时间
     *
     * @param session   会话
     * @param token     验证标识符
     * @param loginInfo 账户信息
     * @return LoginInfo
     */
    private LoginInfo expire(HttpSession session, String token, LoginInfo loginInfo) throws IOException {
        if (null == loginInfo) {
            return null;
        }

        long refreshTime = loginInfo.getRefreshTime().toEpochSecond(ZoneOffset.of("+8"));
        long nowTime = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
        long exMaxTime = refreshTime + loginInfo.getExpiresIn();

        // 已经过了有效期
        if (exMaxTime <= nowTime) {
            remove(session, token);
            return null;
        }

        // 刷新用户数据，暂时写死，单位秒
        if (nowTime - refreshTime > 60) {
            return updateLoginInfo(session, loginInfo);
        } else {
            return loginInfo;
        }
    }

    /**
     * 刷新用户数据
     *
     * @param session   会话
     * @param loginInfo 当前数据
     * @return LoginInfo
     * @throws JsonProcessingException e
     */
    public LoginInfo updateLoginInfo(HttpSession session,
                                     LoginInfo loginInfo) throws JsonProcessingException {
        if(loginInfo.getAccount().getId() == null){
            return loginInfo;
        }

        // 取用户数据
        com.wuyan.web.auth.entity.PubAccount account = pubAccountRepo.findById(loginInfo.getAccount().getId()).get();

        com.wuyan.web.auth.entity.PubUser user;
        if (loginInfo.getUser().getId() == null) {
            user = com.wuyan.web.auth.entity.PubUser.builder().build();
        } else {
            user = pubUserRepo.findById(loginInfo.getUser().getId()).get();
        }

        PubAccount pubAccount = PubAccount.builder().build();
        PubUser pubUser = PubUser.builder().build();
        BeanUtils.copyProperties(account, pubAccount);
        BeanUtils.copyProperties(user, pubUser);

        List<PubRole> roles = getRole(pubAccount);
        loginInfo = LoginInfo.builder()
                .token(loginInfo.getToken())
                .expiresIn(loginInfo.getExpiresIn())
                .account(pubAccount)
                .user(pubUser)
                .dept(null == pubAccount.getDept() ? null : getDept(pubAccount))
                .posts(StringUtils.isBlank(pubAccount.getPost()) ? null : getPosts(pubAccount))
                .roles(roles)
                .apis(getApi(roles))
                .menus(getMenus(roles))
                .loginTime(LocalDateTime.now())
                .refreshTime(LocalDateTime.now())
                .build();

        saveToSession(session, loginInfo);
        saveToCache(loginInfo);

        return loginInfo;
    }
}
